﻿using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using YKXiangQi.Common;
using YKXiangQi.Enums;
using YKXiangQi.EventHandlers;
using YKXiangQi.Models;
using YKXiangQi.Rules;
using YKXiangQi.Utilities;
using Point = YKXiangQi.Models.Point;
using System.Linq;
using System.Collections.Generic;

namespace YKXiangQi.ViewModels
{
    public class BoardViewModel : ViewModelBase
    {
        public event PieceMovedEventHandler PieceMoved;
        public event BoardLeftMouseDownEventHandler LeftMouseDown;
        public event PieceLeftMouseDownEventHandler PieceLeftMouseDown;

        private List<Move> _lastThinkingMoves; //used for re-draw thinking moves when changing the board's size

        private Rule Rule { get; set; }

        public Sides Turn { get; set; }

        public bool IsFlipped { get; set; }

        public BoardMode Mode { get; set; }

        private ViewMode _viewMode;
        public ViewMode ViewMode
        {
            get { return _viewMode; }
            set
            {
                _viewMode = value;
                RaisePropertyChanged(() => ViewMode);
                RenderPieces();
            }
        }

        private BoardSize _size = BoardSize.Medium;
        public BoardSize Size
        {
            get { return _size; }
            set
            {
                _size = value;
                BoardInfo = BoardInfo.FromBoardSize(value);
                RaisePropertyChanged(() => this.Size);
                RaisePropertyChanged(() => ThinkingMoveThickness);

                if (IsBlackThinkingMoveVisible || IsRedThinkingMoveVisible)
                    DrawThinkingMoves(_lastThinkingMoves);
            }
        }

        private BoardInfo _boardInfo;
        public BoardInfo BoardInfo
        {
            get { return _boardInfo; }
            set
            {
                _boardInfo = value;
                RaisePropertyChanged(() => this.BoardInfo);
            }
        }

        public ImageSource Background
        {
            get
            {
                string imagePath = string.Format(@"pack://application:,,,/YKXiangQi;component/Images/{0}/board.jpg",
                                                 Size.ToString());
                return new BitmapImage(new Uri(imagePath));

            }
        }

        private ObservableCollection<PieceViewModel> _pieceViewModels;
        public ObservableCollection<PieceViewModel> PieceViewModels
        {
            get { return _pieceViewModels; }
            set { _pieceViewModels = value; }
        }

        private Piece[,] Pieces { get; set; }

        private PieceViewModel SelectedPiece { get; set; }

        public RelayCommand LeftMouseClickCommand { get; set; }
        public RelayCommand RightMouseClickCommand { get; set; }

        private bool _showThinkingMove;
        public bool ShowThinkingMove
        {
            get { return _showThinkingMove; }
            set 
            {
                _showThinkingMove = value;
                RaisePropertyChanged(() => ShowThinkingMove);
                RaisePropertyChanged(() => IsRedThinkingMoveVisible);
                RaisePropertyChanged(() => IsBlackThinkingMoveVisible);
            }
        }

        private bool _isRedThinkingMoveVisible;
        public bool IsRedThinkingMoveVisible
        {
            get { return _isRedThinkingMoveVisible && ShowThinkingMove ; }
            set
            {
                _isRedThinkingMoveVisible = value;
                RaisePropertyChanged(() => IsRedThinkingMoveVisible);
            }
        }

        private bool _isBlackThinkingMoveVisible;
        public bool IsBlackThinkingMoveVisible
        {
            get { return _isBlackThinkingMoveVisible && ShowThinkingMove; }
            set
            {
                _isBlackThinkingMoveVisible = value;
                RaisePropertyChanged(() => IsBlackThinkingMoveVisible);
            }
        }

        private System.Windows.Point _redThinkingMoveStartPoint;
        public System.Windows.Point RedThinkingMoveStartPoint
        {
            get { return _redThinkingMoveStartPoint; }
            set
            {
                _redThinkingMoveStartPoint = value;
                RaisePropertyChanged(() => RedThinkingMoveStartPoint);
            }
        }

        private System.Windows.Point _redThinkingMoveEndPoint;
        public System.Windows.Point RedThinkingMoveEndPoint
        {
            get { return _redThinkingMoveEndPoint; }
            set
            {
                _redThinkingMoveEndPoint = value;
                RaisePropertyChanged(() => RedThinkingMoveEndPoint);
            }
        }

        private System.Windows.Point _blackThinkingMoveStartPoint;
        public System.Windows.Point BlackThinkingMoveStartPoint
        {
            get { return _blackThinkingMoveStartPoint; }
            set
            {
                _blackThinkingMoveStartPoint = value;
                RaisePropertyChanged(() => BlackThinkingMoveStartPoint);
            }
        }

        private System.Windows.Point _blackThinkingMoveEndPoint;
        public System.Windows.Point BlackThinkingMoveEndPoint
        {
            get { return _blackThinkingMoveEndPoint;}
            set
            {
                _blackThinkingMoveEndPoint = value;
                RaisePropertyChanged(() => BlackThinkingMoveEndPoint);
            }
        }

        public double ThinkingMoveThickness
        {
            get
            {
                switch (Size)
                {
                    case BoardSize.Small:
                        return Constant.ThingkingMoveSmallSize;
                    case BoardSize.Medium:
                        return Constant.ThinkingMoveMediumSize;
                    default:
                        return Constant.ThinkingMoveLargeSize;
                }
            }
        }

        public BoardViewModel()
            : this(BoardSize.Medium)
        {
        }

        public BoardViewModel(BoardSize size)
            : this(size, Constant.DefaultFen)
        {
        }

        public BoardViewModel(BoardSize size, string fen)
            : this(size, fen, BoardMode.Normal)
        {
        }

        public BoardViewModel(BoardSize size, string fen, BoardMode mode)
        {
            Rule = new XiangQiRule();
            Size = size;
            Turn = Sides.Red;
            LeftMouseClickCommand = new RelayCommand(OnBoardLeftMouseDown);
            RightMouseClickCommand = new RelayCommand(OnBoardRightMouseDown);
            SetBoard(fen);
            Mode = mode;
            ShowThinkingMove = true;
            //IsRedThinkingMoveVisible = IsBlackThinkingMoveVisible = true;
            //RedThinkingMoveEndPoint = BlackThinkingMoveEndPoint = new System.Windows.Point(0, 150);
        }

        public void Refresh()
        {
            RaisePropertyChanged(() => Background);
            RenderPieces();
            if (IsBlackThinkingMoveVisible || IsRedThinkingMoveVisible)
                DrawThinkingMoves(_lastThinkingMoves);
        }

        private void InitPiecesFromFen(string fen)
        {
            var fenInfo = FenReader.Read(fen);
            this.Pieces = fenInfo.Pieces;
            
            if (Rule != null)
                Rule.Pieces = fenInfo.Pieces;
        }

        private void RenderPieces()
        {
            _pieceViewModels = new ObservableCollection<PieceViewModel>();
            for (int y = 0; y < 10; y++)
            {
                for (int x = 0; x < 9; x++)
                {
                    var piece = Pieces[y, x];
                    if (piece != null)
                    {
                        var pieceViewModel = new PieceViewModel(piece)
                                                 {
                                                     BoardSize = Size,
                                                     X = x,
                                                     Y = y,
                                                     Left = (IsFlipped ? 8 - x : x) * BoardInfo.CellSize + BoardInfo.Offset - BoardInfo.PieceSize / 2,
                                                     Top = (IsFlipped ? 9 - y : y) * BoardInfo.CellSize + BoardInfo.Offset - BoardInfo.PieceSize / 2,
                                                     ViewMode = this.ViewMode
                                                 };
                        pieceViewModel.LeftMouseDown += OnPieceLeftMouseDown;
                        pieceViewModel.RightMouseDown += OnPieceRightMouseDown;
                        _pieceViewModels.Add(pieceViewModel);
                    }
                }
            }

            RaisePropertyChanged(() => this.PieceViewModels);
        }

        private void OnPieceLeftMouseDown(object sender, EventArgs e)
        {
            var pieceViewModel = sender as PieceViewModel;
            if (pieceViewModel == null) return;

            if (PieceLeftMouseDown != null)
            {
                var args = new PieceLeftMouseDownEventArgs()
                            {
                                Handled = false,
                                Piece = pieceViewModel.Piece
                            };
                PieceLeftMouseDown(this, args);
                if(args.Handled)
                    return;
            }

            if (Mode == BoardMode.ReadOnly) return;

            if (Mode == BoardMode.Editing)
            {
                if(SelectedPiece != null)
                    SelectedPiece.State = PieceState.Normal;
                pieceViewModel.State = PieceState.Selected;
                SelectedPiece = pieceViewModel;
            }
            else
            {
                if (SelectedPiece != null)
                {
                    if (SelectedPiece.Side == pieceViewModel.Side)
                    {
                        if (SelectedPiece != null)
                            SelectedPiece.State = PieceState.Normal;

                        pieceViewModel.State = PieceState.Selected;
                        SelectedPiece = pieceViewModel;
                    }
                    else
                    {
                        var destination = new Point(pieceViewModel.X, pieceViewModel.Y);
                        MovePiece(new Point(SelectedPiece.X, SelectedPiece.Y), destination);
                    }
                }
                else
                {
                    if (pieceViewModel.Side != Turn)
                        return;

                    var lastMovePiece = PieceViewModels.FirstOrDefault(piece => piece.State == PieceState.LastMove);
                    if (lastMovePiece != null)
                        lastMovePiece.State = PieceState.Normal;

                    pieceViewModel.State = PieceState.Selected;
                    SelectedPiece = pieceViewModel;
                }
            }
        }

        private void OnPieceRightMouseDown(object sender, EventArgs e)
        {
            if (Mode != BoardMode.Editing) return;

            var pieceViewModel = sender as PieceViewModel;
            if (pieceViewModel == null) return;

            if(pieceViewModel.Piece is King) return;

            Pieces[pieceViewModel.Y, pieceViewModel.X] = null;
            PieceViewModels.Remove(pieceViewModel);
        }

        private void OnBoardLeftMouseDown(object param)
        {
            const int offset = 10;
            var rect = new Rect(BoardInfo.Offset - offset, BoardInfo.Offset - offset,
                                BoardInfo.Width - 2 * (BoardInfo.Offset - offset),
                                BoardInfo.Height - 2 * (BoardInfo.Offset - offset));
            var mousePosition = Mouse.GetPosition((IInputElement)param);
            if (!rect.Contains(mousePosition)) return;

            var boardPosition =
                ConvertFromRealWorldPositionToBoardPosition(new Point()
                                                                {
                                                                    X = (int)mousePosition.X,
                                                                    Y = (int)mousePosition.Y

                                                                });

            if(LeftMouseDown != null)
            {
                var e = new BoardLeftMouseDownEventArgs()
                            {
                                Handled = false,
                                Position = boardPosition
                            };
                LeftMouseDown(this, e);
                if (e.Handled)
                    return;
            }

            if(Mode == BoardMode.ReadOnly) return;
            if (SelectedPiece == null) return;

            if (IsFlipped)
            {
                boardPosition.X = 8 - boardPosition.X;
                boardPosition.Y = 9 - boardPosition.Y;
            }

            if (Mode == BoardMode.Editing)
                MovePieceInEditingMode(new Point(SelectedPiece.X, SelectedPiece.Y), boardPosition);
            else
                MovePiece(new Point(SelectedPiece.X, SelectedPiece.Y), boardPosition);
        }

        private void OnBoardRightMouseDown(object param)
        {
            if(SelectedPiece!=null)
                SelectedPiece.State = PieceState.Normal;
            SelectedPiece = null;
        }

        private Point ConvertFromRealWorldPositionToBoardPosition(Point position)
        {
            var result = new Point();

            double x = position.X - BoardInfo.Offset;
            double y = position.Y - BoardInfo.Offset;
            result.X = (int)(x / BoardInfo.CellSize);
            result.Y = (int)(y / BoardInfo.CellSize);

            if ((x % BoardInfo.CellSize) >= BoardInfo.CellSize / 2)
                result.X++;
            if ((y % BoardInfo.CellSize) >= BoardInfo.CellSize / 2)
                result.Y++;

            return result;
        }

        private Point ConvertFromBoardPositionToRealWorldPosition(Point position)
        {
            var result = new Point()
                             {
                                 X = position.X * BoardInfo.CellSize + BoardInfo.Offset - BoardInfo.PieceSize / 2,
                                 Y = position.Y * BoardInfo.CellSize + BoardInfo.Offset - BoardInfo.PieceSize / 2
                             };
            return result;
        }

        private void MovePieceInEditingMode(Point from, Point to)
        {
            if(SelectedPiece == null) return;
            if (!IsLegalPosition(SelectedPiece, to)) return;
            if (Pieces[to.Y, to.X] != null) return;

            var positionInRealWorld = ConvertFromBoardPositionToRealWorldPosition(to);
            Pieces[from.Y, from.X] = null;
            Pieces[to.Y, to.X] = SelectedPiece.Piece;
            SelectedPiece.Position = to;
            SelectedPiece.Left = positionInRealWorld.X;
            SelectedPiece.Top = positionInRealWorld.Y;

        }

        private bool IsLegalPosition(PieceViewModel pieceViewModel, Point position)
        {
            var legalPosition = new int[,]
                                    {
                                        {1, 1, 5, 19, 3, 19, 5, 1, 1},
                                        {1, 1, 1, 3, 19, 3, 1, 1, 1},
                                        {5, 1, 1, 19, 7, 19, 1, 1, 5},
                                        {9, 1, 9, 1, 9, 1, 9, 1, 9},
                                        {9, 1, 13, 1, 9, 1, 13, 1, 9},
                                        {9, 9, 9, 9, 9, 9, 9, 9, 9},
                                        {9, 9, 9, 9, 9, 9, 9, 9, 9},
                                        {9, 9, 9, 9, 9, 9, 9, 9, 9},
                                        {9, 9, 9, 9, 9, 9, 9, 9, 9},
                                        {9, 9, 9, 9, 9, 9, 9, 9, 9}
                                    };
            var ny = pieceViewModel.Piece.Side == Sides.Black ? position.Y : 9 - position.Y;
            return (legalPosition[ny, position.X] & pieceViewModel.Piece.PieceMaskValue) != 0;
        }

        public void AddPiece(PieceViewModel pieceViewModel, Point position)
        {
            if (!IsLegalPosition(pieceViewModel, position))
                return;

            var totalPiecesWithSameType =
                PieceViewModels.Count(item => item.Piece.GetType() == pieceViewModel.Piece.GetType()
                                              && item.Side == pieceViewModel.Side);
            int numOfAllowedPieces = pieceViewModel.Piece is Pawn ? 5 : 2;
            if (totalPiecesWithSameType >= numOfAllowedPieces)
                return;

            var positionInRealWorld = ConvertFromBoardPositionToRealWorldPosition(position);
            Pieces[position.Y, position.X] = pieceViewModel.Piece;
            pieceViewModel.Position = position;
            pieceViewModel.Left = positionInRealWorld.X;
            pieceViewModel.Top = positionInRealWorld.Y;
            pieceViewModel.LeftMouseDown += OnPieceLeftMouseDown;
            pieceViewModel.RightMouseDown += OnPieceRightMouseDown;

            PieceViewModels.Add(pieceViewModel);
        }

        public bool MovePiece(Point from, Point to, PieceMovedType movedType = PieceMovedType.Manual)
        {
            foreach (var pieceViewModel in PieceViewModels)
            {
                if (pieceViewModel.State == PieceState.LastMove
                    || pieceViewModel.State == PieceState.Selected)
                    pieceViewModel.State = PieceState.Normal;
            }
            
            var selectedPiece = PieceViewModels.FirstOrDefault(piece => piece.X == from.X && piece.Y == from.Y);
            if (selectedPiece == null) return false;

            var move = new Move()
                {
                    From = from,
                    To = to
                };
            
            if (Rule.IsValidMove(move))
            {
                var capturedPiece = PieceViewModels.FirstOrDefault(piece => piece.X == to.X && piece.Y == to.Y);
                if (capturedPiece != null)
                    Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal,
                                                                         (Action)(() => PieceViewModels.Remove(capturedPiece)));
                    

                var newPos = new Point((IsFlipped ? 8 - to.X : to.X),
                                       (IsFlipped ? 9 - to.Y : to.Y));
                var newPosInRealWorld = ConvertFromBoardPositionToRealWorldPosition(newPos);
                Pieces[from.Y, from.X] = null;
                Pieces[to.Y, to.X] = selectedPiece.Piece;

                selectedPiece.Left = newPosInRealWorld.X;
                selectedPiece.Top = newPosInRealWorld.Y;
                selectedPiece.X = to.X;
                selectedPiece.Y = to.Y;
                selectedPiece.State = PieceState.LastMove;
                SelectedPiece = null;

                Turn = Turn == Sides.Red ? Sides.Black : Sides.Red;
                if (PieceMoved != null)
                {
                    var args = new PieceMovedEventArgs()
                                   {
                                       Piece = selectedPiece,
                                       From =  from,
                                       To = to,
                                       Type = movedType
                                   };
                    PieceMoved(this, args);
                }
            }

            return true;
        }

        public string GetCurrentFen()
        {
            return Helper.GetFen(Pieces, Turn);
        }

        public void SetBoard(string fen)
        {
            InitPiecesFromFen(fen);
            RenderPieces();
        }

        public void ClearSelectedPiece()
        {
            if(SelectedPiece!=null)
                SelectedPiece.State = PieceState.Normal;
            SelectedPiece = null;
        }

        public void NewGame()
        {
            Reset();
            SetBoard(Constant.DefaultFen);
        }

        public void Reset()
        {
            Turn = Sides.Red;
            SelectedPiece = null;
        }

        public void SetBoardPosition(string fen, Sides side)
        {
            Reset();
            SetBoard(fen);
            Turn = side;
        }

        public void DrawThinkingMoves(List<Move> moves)
        {
            _lastThinkingMoves = moves;

            if (moves.Count > 0)
            {
                var x1 = (IsFlipped ? 8 - moves[0].From.X : moves[0].From.X)*BoardInfo.CellSize + BoardInfo.Offset;
                var y1 = (IsFlipped ? 9 - moves[0].From.Y : moves[0].From.Y)*BoardInfo.CellSize + BoardInfo.Offset;
                var x2 = (IsFlipped ? 8 - moves[0].To.X : moves[0].To.X)*BoardInfo.CellSize + BoardInfo.Offset;
                var y2 = (IsFlipped ? 9 - moves[0].To.Y : moves[0].To.Y)*BoardInfo.CellSize + BoardInfo.Offset;

                if (Turn == Sides.Red)
                {
                    RedThinkingMoveStartPoint = new System.Windows.Point(x1, y1);
                    RedThinkingMoveEndPoint = new System.Windows.Point(x2, y2);
                    IsRedThinkingMoveVisible = true;
                }
                else
                {
                    BlackThinkingMoveStartPoint = new System.Windows.Point(x1, y1);
                    BlackThinkingMoveEndPoint = new System.Windows.Point(x2, y2);
                    IsBlackThinkingMoveVisible = true;
                }
            }

            if (moves.Count > 1)
            {
                var x1 = (IsFlipped ? 8 - moves[1].From.X : moves[1].From.X)*BoardInfo.CellSize + BoardInfo.Offset;
                var y1 = (IsFlipped ? 9 - moves[1].From.Y : moves[1].From.Y)*BoardInfo.CellSize + BoardInfo.Offset;
                var x2 = (IsFlipped ? 8 - moves[1].To.X : moves[1].To.X)*BoardInfo.CellSize + BoardInfo.Offset;
                var y2 = (IsFlipped ? 9 - moves[1].To.Y : moves[1].To.Y)*BoardInfo.CellSize + BoardInfo.Offset;

                if (Turn == Sides.Black)
                {
                    RedThinkingMoveStartPoint = new System.Windows.Point(x1, y1);
                    RedThinkingMoveEndPoint = new System.Windows.Point(x2, y2);
                    IsRedThinkingMoveVisible = true;
                }
                else
                {
                    BlackThinkingMoveStartPoint = new System.Windows.Point(x1, y1);
                    BlackThinkingMoveEndPoint = new System.Windows.Point(x2, y2);
                    IsBlackThinkingMoveVisible = true;
                }
            }

        }

        public void ClearThinkingMoves()
        {
            IsRedThinkingMoveVisible = false;
            IsBlackThinkingMoveVisible = false;
        }
    }
}
